<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <style>
    #app {
      margin: 0 auto;
      width: 500px;
      height: 400px;
      background: #efefef;
    }

    .bar {
      fill: green;
    }

    .tool {
      text-align: center;
    }

    #tooltip {
      display: none;
      background: #999999;
      color: white;
      border-radius: 5px;
      width: 80px;
      height: 80px;
      position: absolute;
      left: 0;
      top: 0;
    }
  </style>
</head>

<body>

  <div id="tooltip">

  </div>

  <div style="text-align: center;">
    <svg id="app"></svg>
  </div>
  <hr>
  <div class="tool">
    <button id="btn-sort">排序</button>
    <button id="btn-add">添加</button>
    <button id="btn-update">更新</button>
  </div>

  <script>
    const SVG_HEIGHT = 400;
    const SVG_WIDTH = 500;
    const MARGIN = { TOP: 30, RIGHT: 30, LEFT: 30, BOTTOM: 30 }
    var datalist = [20, 30, 40, 50, 15];
    var sort_flag = false;

    var container = d3.select("#app");

    //x轴，序数比例尺，动态计算：bar的宽度，x方向的坐标
    var xScale = d3.scaleBand()
      .domain(d3.range(datalist.length))
      .range([0, SVG_WIDTH - MARGIN.LEFT - MARGIN.RIGHT])
      .paddingInner(0.1)


    //y轴的线性比例尺
    var yScaleLinear = d3.scaleLinear()
      .domain([0, d3.max(datalist)])
      .range([0, SVG_HEIGHT - MARGIN.TOP - MARGIN.BOTTOM])

    var yScale = d3.scaleLinear()
      .domain([0, d3.max(datalist)])
      .range([SVG_HEIGHT - MARGIN.TOP - MARGIN.BOTTOM, 0])

    var axisBottom = d3.axisBottom(xScale);

    axisBottom(
      container.append('g')
        .attr('id', 'x_axis')
        .attr('transform', `translate(${MARGIN.LEFT},${SVG_HEIGHT - MARGIN.TOP})`)
    )

    var axisLeft = d3.axisLeft(yScale);
    axisLeft(
      container.append('g')
        .attr('id', 'y_axis')
        .attr('transform', `translate(${MARGIN.LEFT},${MARGIN.TOP})`)
    )

    function renderRect() {

      var update = container.selectAll('rect').data(datalist)

      //添加新的rect
      update.enter()
        .append('rect')
        .classed('bar', true);

      update.exit().remove();


      container.selectAll('rect')
        .attr('x', function (d, i) {
          return xScale(i) + MARGIN.LEFT;
        })
        .attr('width', function (d, i) {
          return xScale.bandwidth()
        })
        .attr('y', function (d, i) {
          return SVG_HEIGHT - MARGIN.TOP;
        })
        .attr('height', function (d, i) {
          return 0;
        })
        .on('mouseover', function () {
          d3.select(this).style('fill', 'orange')
        })
        .on('mouseout', function () {
          d3.select(this).style('fill', 'green')
        })
        .on('click', function (d) {
          let { pageX, pageY } = d3.event;
          d3.select("#tooltip")
            .style('display', 'block')
            .style('left', pageX + 'px')
            .style('top', pageY + 'px')
            .text(d)

        })
        .transition()
        .duration(300)
        .delay(function (d, i) {
          return i * 200;
        })
        .attr('y', function (d, i) {
          return SVG_HEIGHT - yScaleLinear(d) - MARGIN.TOP;
        })
        .attr('height', function (d, i) {
          return yScaleLinear(d);
        })


    }

    function renderText() {

      container.select('.textGrop').remove();

      container.append('g').attr('class', 'textGrop')

      var update = d3.select('.textGrop')
        .selectAll('text')
        .data(datalist);

      //添加text
      update.enter()
        .append('text')
        .attr('text-anchor', "middle")
        .text(function (d) {
          return d;
        })

      //更新所有的text的样式、动画
      d3.select('.textGrop').selectAll('text')
        .attr('x', function (d, i) {
          return xScale(i) + MARGIN.LEFT + xScale.bandwidth() / 2;
        })
        .attr('y', function (d, i) {
          return SVG_HEIGHT - MARGIN.TOP;
        })
        .transition()
        .duration(300)
        .delay(function (d, i) {
          return i * 200;
        })
        .attr('y', function (d, i) {
          return SVG_HEIGHT - yScaleLinear(d) - MARGIN.TOP;
        })

    }

    function sort() {

      container.selectAll('rect').sort((a, b) => {
        return sort_flag ? d3.descending(a, b) : d3.ascending(a, b)
      })
        .transition()
        .duration(500)
        .attr('x', (d, i) => {
          return xScale(i) + MARGIN.LEFT;
        })

      container.select('.textGrop').selectAll('text').sort((a, b) => {
        return sort_flag ? d3.descending(a, b) : d3.ascending(a, b)
      })
        .transition()
        .duration(500)
        .attr('x', (d, i) => {
          return xScale(i) + MARGIN.LEFT + xScale.bandwidth() / 2;
        })


      sort_flag = !sort_flag;
    }

    function refershScale() {
      xScale.domain(d3.range(datalist.length));
      yScaleLinear.domain([0, d3.max(datalist)]);
      yScale.domain([0, d3.max(datalist)])

      //刷新x坐标轴
      let xAxis = d3.axisBottom(xScale)
      container.select("#x_axis")
        .transition()
        .duration(500)
        .call(xAxis)

      //刷新y坐标轴
      let yAxis = d3.axisLeft(yScale);
      d3.select('#y_axis')
        .transition()
        .duration(500)
        .call(yAxis)
    }

    function initEvent() {
      d3.select("#btn-sort").on('click', () => {
        sort();
      })

      d3.select("#btn-add").on('click', () => {
        let num = Math.ceil(Math.random() * 100);
        datalist.push(num);
        refershScale();
        renderRect();
        renderText();

      })

      d3.select("#btn-update").on('click', () => {
        mockData();
        refershScale();
        renderRect();
        renderText();
      })

    }

    function mockData() {
      datalist = []
      for (let i = 0; i < 10; i++) {
        let num = Math.ceil(Math.random() * 100);
        datalist.push(num);
      }
    }

    renderRect()
    renderText()
    initEvent();

  </script>

</body>

</html>